package com.xndc.util;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;
import java.util.Calendar;
import java.util.Scanner;
import java.util.Map;
import java.util.HashMap;

/**
 * IEC 104客户端模拟器
 * 直接发送完整的IEC 104报文
 */
@Slf4j
public class Iec104ClientSimulator {
    
    private static final int DEFAULT_PORT = 9000;
    private static final int IEC_104_PORT = 2404;
    
    private EventLoopGroup group;
    private Channel channel;
    private final String host;
    private final int port;
    private final int commonAddress;
    private short sendSequence = 0;
    private short receiveSequence = 0;
    
    public Iec104ClientSimulator() {
        this("localhost", DEFAULT_PORT, 1);
    }
    
    public Iec104ClientSimulator(String host, int port, int commonAddress) {
        this.host = host;
        this.port = port;
        this.commonAddress = commonAddress;
    }
    
    public void connect() throws Exception {
        log.info("正在连接到IEC 104服务器 {}:{}", host, port);
        group = new NioEventLoopGroup();
        
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .option(ChannelOption.SO_KEEPALIVE, true)
                    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            log.debug("初始化通道: {}", ch.remoteAddress());
                            ch.pipeline().addLast(new SimpleChannelInboundHandler<Object>() {
                                @Override
                                protected void channelRead0(ChannelHandlerContext ctx, Object msg) {
                                    if (msg instanceof byte[]) {
                                        byte[] data = (byte[]) msg;
                                        log.info("收到响应: {}", bytesToHex(data));
                                    }
                                }
                                
                                @Override
                                public void channelActive(ChannelHandlerContext ctx) {
                                    log.info("连接已建立，发送IEC104启动帧");
                                    // 发送启动帧
                                    byte[] startFrame = createStartFrame();
                                    ctx.writeAndFlush(startFrame);
                                }
                                
                                @Override
                                public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
                                    log.error("通道异常: {}", cause.getMessage(), cause);
                                    ctx.close();
                                }
                            });
                        }
                    });
            
            channel = bootstrap.connect(host, port).sync().channel();
            log.info("已连接到服务器: {}:{}", host, port);
            
        } catch (Exception e) {
            log.error("连接服务器失败: {}", e.getMessage(), e);
            close();
            throw e;
        }
    }
    
    /**
     * 创建启动帧
     */
    private byte[] createStartFrame() {
        // 启动帧格式: 68 04 07 00 00 00
        byte[] frame = new byte[6];
        frame[0] = 0x68;  // 启动字符
        frame[1] = 0x04;  // 长度
        frame[2] = 0x07;  // 控制域1
        frame[3] = 0x00;  // 控制域2
        frame[4] = 0x00;  // 控制域3
        frame[5] = 0x00;  // 控制域4
        return frame;
    }
    
    /**
     * 发送时钟同步命令
     */
    public boolean sendClockSyncCommand() {
        if (channel == null || !channel.isActive()) {
            log.error("通道未连接或不活跃，无法发送I帧");
            return false;
        }
        
        try {
            byte[] frame = createClockSyncFrame();
            channel.writeAndFlush(frame);
            log.info("已发送时钟同步命令");
            return true;
        } catch (Exception e) {
            log.error("发送时钟同步命令失败: {}", e.getMessage(), e);
            return false;
        }
    }
    
    /**
     * 创建时钟同步帧
     */
    private byte[] createClockSyncFrame() {
        // 获取当前时间
        Calendar cal = Calendar.getInstance();
        byte[] timeBytes = getCurrentTimeBytes();
        
        // 计算ASDU长度
        int asduLength = 6 + 3 + 7;  // 6字节ASDU头 + 3字节IOA + 7字节时间
        
        // 创建完整帧
        byte[] frame = new byte[2 + asduLength];  // 2字节启动字符和长度 + ASDU长度
        
        // 设置启动字符和长度
        frame[0] = 0x68;
        frame[1] = (byte)asduLength;
        
        // 设置控制域
        frame[2] = (byte)((sendSequence << 1) & 0xFE);  // 发送序列号低字节
        frame[3] = (byte)((sendSequence >> 7) & 0xFF);  // 发送序列号高字节
        frame[4] = (byte)((receiveSequence << 1) & 0xFE);  // 接收序列号低字节
        frame[5] = (byte)((receiveSequence >> 7) & 0xFF);  // 接收序列号高字节
        
        // 设置ASDU
        frame[6] = 0x67;  // 类型标识: 时钟同步命令
        frame[7] = 0x01;  // VSQ: 1个信息对象
        frame[8] = 0x06;  // COT: 激活
        frame[9] = (byte)commonAddress;  // 公共地址
        
        // 设置IOA
        frame[10] = 0x00;  // IOA低字节
        frame[11] = 0x00;  // IOA中字节
        frame[12] = 0x00;  // IOA高字节
        
        // 设置时间
        System.arraycopy(timeBytes, 0, frame, 13, 7);
        
        // 更新发送序列号
        sendSequence = (short)((sendSequence + 1) % 32768);
        
        return frame;
    }
    
    /**
     * 获取当前时间的CP56Time2a格式字节数组
     */
    private byte[] getCurrentTimeBytes() {
        byte[] timeBytes = new byte[7];
        Calendar cal = Calendar.getInstance();
        
        int ms = cal.get(Calendar.MILLISECOND);
        timeBytes[0] = (byte)(ms & 0xFF);
        timeBytes[1] = (byte)((ms >> 8) & 0x03);
        
        int minute = cal.get(Calendar.MINUTE);
        timeBytes[2] = (byte)(minute & 0x3F);
        
        int hour = cal.get(Calendar.HOUR_OF_DAY);
        timeBytes[3] = (byte)(hour & 0x1F);
        
        int day = cal.get(Calendar.DAY_OF_MONTH);
        timeBytes[4] = (byte)(day & 0x1F);
        
        int month = cal.get(Calendar.MONTH) + 1;
        timeBytes[5] = (byte)(month & 0x0F);
        
        int year = cal.get(Calendar.YEAR) % 100;
        timeBytes[6] = (byte)(year & 0x7F);
        
        return timeBytes;
    }
    
    /**
     * 发送自定义字节数组
     */
    public boolean sendCustomBytes(byte[] data) {
        if (channel == null || !channel.isActive()) {
            log.error("通道未连接或不活跃，无法发送数据");
            return false;
        }
        
        try {
            channel.writeAndFlush(data);
            log.info("已发送自定义数据: {}", bytesToHex(data));
            return true;
        } catch (Exception e) {
            log.error("发送自定义数据失败: {}", e.getMessage(), e);
            return false;
        }
    }
    
    /**
     * 将字节数组转换为十六进制字符串
     */
    private String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02X ", b));
        }
        return sb.toString().trim();
    }
    
    public void close() {
        if (channel != null) {
            try {
                channel.close();
            } catch (Exception e) {
                log.warn("关闭通道异常: {}", e.getMessage());
            }
            channel = null;
        }
        
        if (group != null) {
            try {
                group.shutdownGracefully();
            } catch (Exception e) {
                log.warn("关闭事件循环组异常: {}", e.getMessage());
            }
            group = null;
        }
        
        log.info("已关闭连接");
    }
    
    /**
     * 创建单点信息帧
     * @param ioa 信息对象地址
     * @param value 值 (true=开, false=关)
     * @param cot 传送原因
     */
    public byte[] createSinglePointFrame(int ioa, boolean value, byte cot) {
        // 计算ASDU长度 (不包括启动字符和长度字段)
        int asduLength = 4 + 6 + 3 + 1;  // 4字节控制域 + 6字节ASDU头 + 3字节IOA + 1字节值
        
        // 创建完整帧 (2字节启动字符和长度 + ASDU长度)
        byte[] frame = new byte[2 + asduLength];
        
        // 设置启动字符和长度
        frame[0] = 0x68;
        frame[1] = (byte)asduLength;
        
        // 设置控制域
        frame[2] = (byte)((sendSequence & 0x7F) << 1);  // 发送序列号低7位
        frame[3] = (byte)((sendSequence >> 7) & 0x7F);  // 发送序列号高7位
        frame[4] = (byte)((receiveSequence & 0x7F) << 1);  // 接收序列号低7位
        frame[5] = (byte)((receiveSequence >> 7) & 0x7F);  // 接收序列号高7位
        
        // 设置ASDU
        frame[6] = 0x01;  // 类型标识: 单点信息
        frame[7] = 0x01;  // VSQ: 1个信息对象
        frame[8] = cot;   // 传送原因
        frame[9] = (byte)commonAddress;  // 公共地址
        
        // 设置IOA (3字节，小端序)
        frame[10] = (byte)(ioa & 0xFF);        // 低字节
        frame[11] = (byte)((ioa >> 8) & 0xFF); // 中字节
        frame[12] = (byte)((ioa >> 16) & 0xFF); // 高字节
        
        // 设置值
        frame[13] = (byte)(value ? 0x01 : 0x00);
        
        // 更新发送序列号
        sendSequence = (short)((sendSequence + 1) % 32768);
        
        return frame;
    }
    
    /**
     * 创建双点信息帧
     * @param ioa 信息对象地址
     * @param value 值 (0=中间状态, 1=确定开, 2=确定关, 3=不确定)
     * @param cot 传送原因
     */
    public byte[] createDoublePointFrame(int ioa, int value, byte cot) {
        // 计算ASDU长度
        int asduLength = 6 + 3 + 1;  // 6字节ASDU头 + 3字节IOA + 1字节值
        
        // 创建完整帧
        byte[] frame = new byte[2 + asduLength];
        
        // 设置启动字符和长度
        frame[0] = 0x68;
        frame[1] = (byte)asduLength;
        
        // 设置控制域
        frame[2] = (byte)((sendSequence & 0x7F) << 1);  // 发送序列号低7位
        frame[3] = (byte)((sendSequence >> 7) & 0x7F);  // 发送序列号高7位
        frame[4] = (byte)((receiveSequence & 0x7F) << 1);  // 接收序列号低7位
        frame[5] = (byte)((receiveSequence >> 7) & 0x7F);  // 接收序列号高7位
        
        // 设置ASDU
        frame[6] = 0x03;  // 类型标识: 双点信息
        frame[7] = 0x01;  // VSQ: 1个信息对象
        frame[8] = cot;   // 传送原因
        frame[9] = (byte)commonAddress;  // 公共地址
        
        // 设置IOA
        frame[10] = (byte)(ioa & 0xFF);
        frame[11] = (byte)((ioa >> 8) & 0xFF);
        frame[12] = (byte)((ioa >> 16) & 0xFF);
        
        // 设置值
        frame[13] = (byte)(value & 0x03);
        
        // 更新发送序列号
        sendSequence = (short)((sendSequence + 1) % 32768);
        
        return frame;
    }
    
    /**
     * 创建测量值帧（短浮点数）
     * @param ioa 信息对象地址
     * @param value 测量值
     * @param cot 传送原因
     */
    public byte[] createMeasurementFrame(int ioa, float value, byte cot) {
        // 计算ASDU长度
        int asduLength = 6 + 3 + 4;  // 6字节ASDU头 + 3字节IOA + 4字节浮点数
        
        // 创建完整帧
        byte[] frame = new byte[2 + asduLength];
        
        // 设置启动字符和长度
        frame[0] = 0x68;
        frame[1] = (byte)asduLength;
        
        // 设置控制域
        frame[2] = (byte)((sendSequence & 0x7F) << 1);  // 发送序列号低7位
        frame[3] = (byte)((sendSequence >> 7) & 0x7F);  // 发送序列号高7位
        frame[4] = (byte)((receiveSequence & 0x7F) << 1);  // 接收序列号低7位
        frame[5] = (byte)((receiveSequence >> 7) & 0x7F);  // 接收序列号高7位
        
        // 设置ASDU
        frame[6] = 0x0D;  // 类型标识: 测量值-短浮点数
        frame[7] = 0x01;  // VSQ: 1个信息对象
        frame[8] = cot;   // 传送原因
        frame[9] = (byte)commonAddress;  // 公共地址
        
        // 设置IOA
        frame[10] = (byte)(ioa & 0xFF);
        frame[11] = (byte)((ioa >> 8) & 0xFF);
        frame[12] = (byte)((ioa >> 16) & 0xFF);
        
        // 设置浮点数值
        int bits = Float.floatToIntBits(value);
        frame[13] = (byte)(bits & 0xFF);
        frame[14] = (byte)((bits >> 8) & 0xFF);
        frame[15] = (byte)((bits >> 16) & 0xFF);
        frame[16] = (byte)((bits >> 24) & 0xFF);
        
        // 更新发送序列号
        sendSequence = (short)((sendSequence + 1) % 32768);
        
        return frame;
    }
    
    /**
     * 创建总召唤命令帧
     * @param qoi 召唤限定词
     */
    public byte[] createGeneralInterrogationFrame(byte qoi) {
        // 计算ASDU长度
        int asduLength = 6 + 3 + 1;  // 6字节ASDU头 + 3字节IOA + 1字节QOI
        
        // 创建完整帧
        byte[] frame = new byte[2 + asduLength];
        
        // 设置启动字符和长度
        frame[0] = 0x68;
        frame[1] = (byte)asduLength;
        
        // 设置控制域
        frame[2] = (byte)((sendSequence & 0x7F) << 1);  // 发送序列号低7位
        frame[3] = (byte)((sendSequence >> 7) & 0x7F);  // 发送序列号高7位
        frame[4] = (byte)((receiveSequence & 0x7F) << 1);  // 接收序列号低7位
        frame[5] = (byte)((receiveSequence >> 7) & 0x7F);  // 接收序列号高7位
        
        // 设置ASDU
        frame[6] = 0x64;  // 类型标识: 总召唤命令
        frame[7] = 0x01;  // VSQ: 1个信息对象
        frame[8] = 0x06;  // COT: 激活
        frame[9] = (byte)commonAddress;  // 公共地址
        
        // 设置IOA (总召唤通常使用IOA=0)
        frame[10] = 0x00;
        frame[11] = 0x00;
        frame[12] = 0x00;
        
        // 设置QOI
        frame[13] = qoi;
        
        // 更新发送序列号
        sendSequence = (short)((sendSequence + 1) % 32768);
        
        return frame;
    }
    
    /**
     * 创建单点命令帧
     * @param ioa 信息对象地址
     * @param value 值 (true=开, false=关)
     * @param cot 传送原因
     */
    public byte[] createSingleCommandFrame(int ioa, boolean value, byte cot) {
        // 计算ASDU长度
        int asduLength = 6 + 3 + 1;  // 6字节ASDU头 + 3字节IOA + 1字节值
        
        // 创建完整帧
        byte[] frame = new byte[2 + asduLength];
        
        // 设置启动字符和长度
        frame[0] = 0x68;
        frame[1] = (byte)asduLength;
        
        // 设置控制域
        frame[2] = (byte)((sendSequence & 0x7F) << 1);  // 发送序列号低7位
        frame[3] = (byte)((sendSequence >> 7) & 0x7F);  // 发送序列号高7位
        frame[4] = (byte)((receiveSequence & 0x7F) << 1);  // 接收序列号低7位
        frame[5] = (byte)((receiveSequence >> 7) & 0x7F);  // 接收序列号高7位
        
        // 设置ASDU
        frame[6] = 0x2D;  // 类型标识: 单点命令
        frame[7] = 0x01;  // VSQ: 1个信息对象
        frame[8] = cot;   // 传送原因
        frame[9] = (byte)commonAddress;  // 公共地址
        
        // 设置IOA
        frame[10] = (byte)(ioa & 0xFF);
        frame[11] = (byte)((ioa >> 8) & 0xFF);
        frame[12] = (byte)((ioa >> 16) & 0xFF);
        
        // 设置值
        frame[13] = (byte)(value ? 0x01 : 0x00);
        
        // 更新发送序列号
        sendSequence = (short)((sendSequence + 1) % 32768);
        
        return frame;
    }
    
    /**
     * 创建双点命令帧
     * @param ioa 信息对象地址
     * @param value 值 (0=中间状态, 1=确定开, 2=确定关, 3=不确定)
     * @param cot 传送原因
     */
    public byte[] createDoubleCommandFrame(int ioa, int value, byte cot) {
        // 计算ASDU长度
        int asduLength = 6 + 3 + 1;  // 6字节ASDU头 + 3字节IOA + 1字节值
        
        // 创建完整帧
        byte[] frame = new byte[2 + asduLength];
        
        // 设置启动字符和长度
        frame[0] = 0x68;
        frame[1] = (byte)asduLength;
        
        // 设置控制域
        frame[2] = (byte)((sendSequence & 0x7F) << 1);  // 发送序列号低7位
        frame[3] = (byte)((sendSequence >> 7) & 0x7F);  // 发送序列号高7位
        frame[4] = (byte)((receiveSequence & 0x7F) << 1);  // 接收序列号低7位
        frame[5] = (byte)((receiveSequence >> 7) & 0x7F);  // 接收序列号高7位
        
        // 设置ASDU
        frame[6] = 0x2E;  // 类型标识: 双点命令
        frame[7] = 0x01;  // VSQ: 1个信息对象
        frame[8] = cot;   // 传送原因
        frame[9] = (byte)commonAddress;  // 公共地址
        
        // 设置IOA
        frame[10] = (byte)(ioa & 0xFF);
        frame[11] = (byte)((ioa >> 8) & 0xFF);
        frame[12] = (byte)((ioa >> 16) & 0xFF);
        
        // 设置值
        frame[13] = (byte)(value & 0x03);
        
        // 更新发送序列号
        sendSequence = (short)((sendSequence + 1) % 32768);
        
        return frame;
    }
    
    /**
     * 创建设点命令帧（短浮点数）
     * @param ioa 信息对象地址
     * @param value 设定值
     * @param cot 传送原因
     */
    public byte[] createSetpointCommandFrame(int ioa, float value, byte cot) {
        // 计算ASDU长度
        int asduLength = 6 + 3 + 4;  // 6字节ASDU头 + 3字节IOA + 4字节浮点数
        
        // 创建完整帧
        byte[] frame = new byte[2 + asduLength];
        
        // 设置启动字符和长度
        frame[0] = 0x68;
        frame[1] = (byte)asduLength;
        
        // 设置控制域
        frame[2] = (byte)((sendSequence & 0x7F) << 1);  // 发送序列号低7位
        frame[3] = (byte)((sendSequence >> 7) & 0x7F);  // 发送序列号高7位
        frame[4] = (byte)((receiveSequence & 0x7F) << 1);  // 接收序列号低7位
        frame[5] = (byte)((receiveSequence >> 7) & 0x7F);  // 接收序列号高7位
        
        // 设置ASDU
        frame[6] = 0x3A;  // 类型标识: 设点命令-短浮点数
        frame[7] = 0x01;  // VSQ: 1个信息对象
        frame[8] = cot;   // 传送原因
        frame[9] = (byte)commonAddress;  // 公共地址
        
        // 设置IOA
        frame[10] = (byte)(ioa & 0xFF);
        frame[11] = (byte)((ioa >> 8) & 0xFF);
        frame[12] = (byte)((ioa >> 16) & 0xFF);
        
        // 设置浮点数值
        int bits = Float.floatToIntBits(value);
        frame[13] = (byte)(bits & 0xFF);
        frame[14] = (byte)((bits >> 8) & 0xFF);
        frame[15] = (byte)((bits >> 16) & 0xFF);
        frame[16] = (byte)((bits >> 24) & 0xFF);
        
        // 更新发送序列号
        sendSequence = (short)((sendSequence + 1) % 32768);
        
        return frame;
    }
    
    public static void main(String[] args) {
        Iec104ClientSimulator client = new Iec104ClientSimulator();
        try {
            client.connect();
            
            // 发送启动帧
            byte[] startFrame = client.createStartFrame();
            client.sendCustomBytes(startFrame);
            
            // 等待用户输入
            Scanner scanner = new Scanner(System.in);
            boolean running = true;
            
            while (running) {
                System.out.println("\n===== IEC 104 I帧测试菜单 =====");
                System.out.println("1. 发送单点信息 (IOA=1, 状态=开)");
                System.out.println("2. 发送双点信息 (IOA=1, 状态=接通)");
                System.out.println("3. 发送测量值 (IOA=1, 值=220.5)");
                System.out.println("4. 发送总召唤命令 (QOI=20)");
                System.out.println("5. 发送时钟同步命令");
                System.out.println("6. 发送单点命令 (IOA=1, 命令=开)");
                System.out.println("7. 发送双点命令 (IOA=1, 命令=接通)");
                System.out.println("8. 发送设点命令 (IOA=1, 值=230.0)");
                System.out.println("0. 退出");
                System.out.print("请选择操作 [0-8]: ");
                
                int choice;
                try {
                    String input = scanner.nextLine().trim();
                    if (input.isEmpty()) continue;
                    choice = Integer.parseInt(input);
                } catch (NumberFormatException e) {
                    System.out.println("输入无效，请输入数字");
                    continue;
                }
                
                switch (choice) {
                    case 0:
                        running = false;
                        break;
                    case 1:
                        byte[] singlePointFrame = client.createSinglePointFrame(1, true, (byte)3);
                        client.sendCustomBytes(singlePointFrame);
                        break;
                    case 2:
                        byte[] doublePointFrame = client.createDoublePointFrame(1, 2, (byte)3);
                        client.sendCustomBytes(doublePointFrame);
                        break;
                    case 3:
                        byte[] measurementFrame = client.createMeasurementFrame(1, 220.5f, (byte)3);
                        client.sendCustomBytes(measurementFrame);
                        break;
                    case 4:
                        byte[] interrogationFrame = client.createGeneralInterrogationFrame((byte)20);
                        client.sendCustomBytes(interrogationFrame);
                        break;
                    case 5:
                        client.sendClockSyncCommand();
                        break;
                    case 6:
                        byte[] singleCommandFrame = client.createSingleCommandFrame(1, true, (byte)6);
                        client.sendCustomBytes(singleCommandFrame);
                        break;
                    case 7:
                        byte[] doubleCommandFrame = client.createDoubleCommandFrame(1, 2, (byte)6);
                        client.sendCustomBytes(doubleCommandFrame);
                        break;
                    case 8:
                        byte[] setpointFrame = client.createSetpointCommandFrame(1, 230.0f, (byte)6);
                        client.sendCustomBytes(setpointFrame);
                        break;
                    default:
                        System.out.println("无效选择，请输入0-8之间的数字");
                }
            }
            
        } catch (Exception e) {
            log.error("运行异常: {}", e.getMessage(), e);
        } finally {
            client.close();
        }
    }
} 